Repository navigation
assignments_to_experiments: convert values that ramp with time, via a rate rule - #557
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #557 +/- ##
==========================================
+ Coverage 78.85% 78.91% +0.06%
==========================================
Files 69 69
Lines 8043 8071 +28
Branches 1445 1449 +4
==========================================
+ Hits 6342 6369 +27
- Misses 1214 1215 +1
Partials 487 487 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
a9279e1 to
d8b0bbf
Compare
|
see also discussion in #556 |
d8b0bbf to
e960510
Compare
dweindl
left a comment
There was a problem hiding this comment.
Looks good.
Since that function is getting longer and longer, how about converting that into a class and moving some parts to some private helpers (find available ID, add parameter, add RateRule, ...) and encapsulating the currently free non-reusable helpers like _changes? So far, this is unreleased and wouldn't really break much. (Independent of this PR to keep the diff more readable.)
|
On second thought, turning that into a class is probably not really helpful, because it's just one operation with one result. Slimming down that function would still be good. |
422e254 to
304dfe7
Compare
… rate rule A condition change is applied once at the start of its period, with `time` set to the period's start, so a value that changes with time within a period cannot be a condition change on a constant target, and such assignments were rejected. Fujita_SciSignal2010's EGF input is one: `piecewise(EGF_rate * time + EGF_0, time <= EGF_end, 0)`, steps in training and ramps in its held-out data. A value may now change linearly with time. The target then becomes a differential entity, with the rate rule `d target/dt = <target>_slope` and a new constant parameter for the slope, and each period sets both the target's value at the period start and its slope. During preequilibration the value is held at its value at time 0 with slope zero, and the first period after preequilibration sets the slope again. An assignment that only switches between constant values converts exactly as before, as a constant parameter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MNJePorYs9zmPYMam6g4EP
…ifferential The slope parameter is a placeholder at 0; every ramp's start value and slope, including at time 0, are set through the condition table. Constant model parameters in changes at an experiment's start take their model values, since those changes may only refer to parameter table symbols. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MNJePorYs9zmPYMam6g4EP
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`add_sbml_parameter` checks libsbml's return codes, which the hand-written parameter creation did not. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
54328e6 to
c52a56f
Compare
…#559) A v2 period's `time` overrides the model's initial time, so an experiment starts at its first period after pre-equilibration. `assignments_to_experiments` dropped that period whenever nothing changed there. Either it was redundant, because the model already holds the value before the switch, or its condition only set symbols that the conversion replaced, so the condition was emptied and removed. The experiment then started at its first switch, and measurements before the switch were simulated from the wrong start. For example, `k1 := piecewise(1, time < 5, 2)` converted to `default_experiment` with a single period at t = 5. The existing `test_assignments_to_experiments` asserted that shape: one period at t = 10, beside a measurement at t = 0. ## Changes - Every converted experiment keeps the period that starts it, without conditions if none are left. For `default_experiment`, and for an experiment that only pre-equilibrates, that is the period at t = 0 the converter already adds. The `or experiment.periods[:1]` fallback goes, since the start period is never dropped. - A switch therefore never becomes an experiment's first period, and `CheckInitialChangeSymbols` only checks the first period. A start value that equals the model's value is no longer set just because a switch refers to symbols outside the parameter table. For `level * piecewise(0, time < 10, 1)`, the experiment now has an empty period at 0 and `k1 = level` at 10, where it had `k1 = 0` at 0 and `k1 = level` at 10. A start value that refers to parameter table symbols is still set at the start (as since #556), since the model only holds a number for it. ## Tests - `test_assignments_to_experiments`, `_nested`, `_condition_specific`, `_several_switches` and `_fixed_switching_time` now expect the empty period at t = 0. - New `test_assignments_to_experiments_simulate_before_switch` covers both cases: no experiment, and a condition at t = 0 that only sets the switching time. It converts the problem, turns both the original and the converted problem into SBML events with `ExperimentsToSbmlConverter`, simulates them in BasiCO at t = 0, 1, 2, 4, 6 and 10, and compares the results. They match to 3e-11 relative (the test asserts 1e-6). Without this fix, the converted problem is off by up to 45% before the switch. - The benchmark problems that convert (Brannmark_JBC2010, Fujita_SciSignal2010, Isensee_JCB2018, Raimundez_PCB2020) give the same experiment table, condition table and SBML with and without this change. ## Relation to #557 Stacked on #557; #556 is merged. The bug has been on main since #527. On main the change merges cleanly as text, but main's `test_assignments_to_experiments_several_switches` and `_fixed_switching_time` assert the old shape, with no period at t = 0, and #557 reworks the `redundant` block that this change simplifies. This PR's base moves to main once #557 merges. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Fabian Fröhlich <frohlif@login006.nemo.thecrick.org>
Follows #559. A refactor of `assignments_to_experiments` that does not change its behaviour. The fixes for bugs found while reviewing and stress-testing it are in #574, stacked on this PR. ## Slimming down `assignments_to_experiments` The function had grown to 340 lines, most of them one loop body passing around the parsed assignment, its switches, its value before every switch, the switching times' fixed values and the slope parameter of a ramp. It is now about 75 lines (plus its docstring) that run the steps in order: - `_add_start_periods`: give every experiment the period that starts it, and find the periods that may be dropped; - for each time-dependent assignment rule, a `_PiecewiseAssignment`: - its constructor checks that the assignment is supported and works out the value before every switch, the switching times' fixed values and whether the value ramps; - `replace_in_model()` replaces the rule in the model (the slope parameter and rate rule for a ramp, the target's value); - `changes(experiment)` returns the condition changes that replace it in an experiment. The old module-level `_changes` and `_value_from` became its methods. - `_drop_changes` / `_add_conditions`: replace the condition changes the new conditions cover; - drop empty periods, add the default experiment, lint. The `redundant` / `after_preequilibration` double negative became one decision per period: one branch for the first period, one for the later ones. On the review comment on #557 about not turning this into a class: `assignments_to_experiments` stays a function. `_PiecewiseAssignment` only holds the values of one assignment that the old loop body passed between its parts. If you would rather not have it, it can become a few free functions that take those values. No change in behaviour: on all 40 conversions in the tests, the old and new implementations give the same experiment table, condition table, measurement table, SBML, errors and warnings. The benchmark problems Brannmark_JBC2010, Fujita_SciSignal2010, Isensee_JCB2018, Raimundez_PCB2020 (and Oliveira_NatCommun2021's error) also convert identically. ## Checked - `pytest tests/v2 tests/v1/test_petab.py`: 244 passed; pre-commit clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Fabian Fröhlich <frohlif@login006.nemo.thecrick.org>
Stacked on #556 (merge that first).
assignments_to_experimentsrejects an assignment whose value changes with time within a period, because a condition change is applied once at the start of its period, withtimeset to the period's start: on a constant target it can only step. Fujita_SciSignal2010's EGF input is such an assignment,steps in the training data and ramps (
EGF_0 = 0,EGF_rate > 0) in its held-out data.What. A value may now change linearly with time (its rate of change must not itself depend on time;
time^2,exp(time)are still rejected). The only change to the model is to make the target a differential entity, as the v2 condition table allows targets to be constant or differential entities:d target/dt = <target>_slope, with a new parameter for the slope, a placeholder at 0. Everything else goes through the condition and experiment tables: every period that starts a piece, including the first at time 0, sets both the target's value at the period start and its slope:EGFEGF_slopeEGF_0EGF_endEGF_rateEGF_endThe slope parameter stands in for a
setRateoperation, which earlier v2 drafts had (PEtab-dev/PEtab#581) and which was dropped before release. If it returns, the rate rule becomesd target/dt = 0and the slopes becomesetRaterows.Changes at the start of an experiment may only refer to parameter table symbols, so constant model parameters in them take their model values.
During preequilibration the value is held at its value at time 0 with slope zero (consistent with the existing preequilibration semantics and warning), and the first period after preequilibration sets the slope again. An assignment that only switches between constant values converts exactly as before, as a constant parameter; the rate rule is only introduced where a piece actually ramps.
Checked
Tests: a ramp ending at a switch, Fujita-style pulses and ramps set by the condition table, a ramp after preequilibration;
time^2andexp(time)values rejected.Fujita, PEtab v1 against the v2 upgrade converted by this branch, AMICI at the nominal parameters:
(The test-data problems were loaded without their visualization table, which they share with the training problem and which names datasets their measurement tables lack; the lint rejects them otherwise.)
Brannmark_JBC2010, Isensee_JCB2018 and Raimundez_PCB2020 convert with log-likelihoods identical to assignments_to_experiments: convert assignments that switch more than once #556 (Raimundez with ExperimentsToSbmlConverter: write one flat initial assignment per target #546 merged in, which it needs to import at all).
🤖 Generated with Claude Code
https://claude.ai/code/session_01MNJePorYs9zmPYMam6g4EP